//------------------------------------------------------------
//  Filename: iic_intf.v
//   
//  Author  : wlduan@gmail.com
//  Revise  : 2016-11-26 14:57
//  Description: 
//   
//  Copyright (C) 2014, YRBD, Inc. 					      
//  All Rights Reserved.                                       
//-------------------------------------------------------------
//
`timescale 1ns/1ps
 
module IIC_INTF ( 
    input  wire        clk,  
    input  wire        rst,  
    
    input  wire        iic_addr_size, 
    input  wire [7:0]  iic_devid,
    input  wire [31:0] iic_ctrl,
    output reg  [15:0] iic_data_read,
    output reg         iic_trans_over,

    output wire        iic_sclk,
    inout  wire        iic_sda
);      
//--------------------------------------------------------
wire[6:0]   dev_id      = iic_devid[7:1];      
wire        s_op        = iic_ctrl[24];
wire[15:0]  sub_address = iic_ctrl[23:8];
wire[7:0]   write_data  = iic_ctrl[7:0];
wire        op_start    = iic_ctrl[31];
wire[1:0]   s_start     = 2'b10;
wire[1:0]   s_stop      = 2'b01;
wire        s_nack      = 1'b0;
//--------------------------------------------------------
reg op_start_ff1;
always @(posedge clk) op_start_ff1 <= op_start;
//--------------------------------------------------------
wire posedge_op_start = op_start&(~op_start_ff1);
//--------------------------------------------------------
//  IIC logic
//--------------------------------------------------------
reg[19:0] counter_driver;
//--------------------------------------------------------
reg       ack_detec;
reg[3:0 ] phase_cntr ; 
//--------------------------------------------------------
localparam MAX_TRANS  = 9;
localparam CLK_DIVBIT = 10;
//--------------------------------------------------------
localparam IDLE   = 8'b0000_0001;
localparam START  = 8'b0000_0100;
localparam PH_ID  = 8'b0000_1000;
localparam PH_A0  = 8'b0001_0000;
localparam PH_A1  = 8'b0010_0000;
localparam PH_D0  = 8'b0100_0000;
localparam STOP   = 8'b1000_0000;
//--------------------------------------------------------
reg[7:0] cur_stat;
reg[7:0] nxt_stat;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        cur_stat <= IDLE;
    end 
    else begin 
        cur_stat <= nxt_stat;  
    end 
end 
//--------------------------------------------------------
wire[7:0] PH_AX = (iic_addr_size==1)? PH_A0:PH_A1; 
//--------------------------------------------------------
always @(*) begin
    case (cur_stat)
        IDLE   : begin 
            nxt_stat = (posedge_op_start)? START:IDLE;
        end
        START  : begin 
            nxt_stat = (counter_driver[19:CLK_DIVBIT] >= 2)? PH_ID:START;
        end
        PH_ID  : begin 
            if(counter_driver[19:CLK_DIVBIT] >= MAX_TRANS) begin
                if(ack_detec)begin
                    nxt_stat = ((phase_cntr > 0)? PH_D0:PH_AX);
                end
                else begin
                    nxt_stat = STOP;
                end
            end
            else begin
                nxt_stat = PH_ID;
            end
        end
        PH_A0  : begin 
            if (counter_driver[19:CLK_DIVBIT] >= MAX_TRANS) begin
                if(ack_detec)begin
                    nxt_stat = PH_A1;
                end
                else begin
                    nxt_stat = STOP;
                end
            end
            else begin
                nxt_stat = PH_A0;
            end
        end
        PH_A1  : begin 
            if (counter_driver[19:CLK_DIVBIT] >= MAX_TRANS) begin
                if(ack_detec)begin
                    nxt_stat = ((s_op == 1)? STOP:PH_D0);
                end
                else begin
                    nxt_stat = STOP;
                end
            end
            else begin
                nxt_stat = PH_A1;
            end       
        end
        PH_D0  : begin 
            if (counter_driver[19:CLK_DIVBIT] >= MAX_TRANS) begin
                nxt_stat = STOP;
            end
            else begin
                nxt_stat = PH_D0;
            end
        end
        STOP   : begin 
            nxt_stat = (counter_driver[19:CLK_DIVBIT] >= 2)? 
                       ((((s_op == 1)&&(phase_cntr > 0))||(s_op == 0))? IDLE:START):STOP;
        end  
        default: begin
            nxt_stat = IDLE;
        end      
    endcase 
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        iic_trans_over <= 1'b0;
    end
    else if((cur_stat == STOP)&&(nxt_stat == IDLE))begin
        iic_trans_over <= 1'b1;
    end
    else begin
        iic_trans_over <= 1'b0;
    end
end
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        phase_cntr <= 4'b0;        
    end 
    else if(cur_stat == IDLE)begin
        phase_cntr <= 4'b0;        
    end
    else if(cur_stat == nxt_stat) begin
        phase_cntr <= phase_cntr;   
    end
    else if(cur_stat == STOP)begin 
        phase_cntr <= phase_cntr + 4'b1;    
    end 
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        counter_driver     <= 20'b0;        
    end 
    else if(cur_stat == IDLE) begin 
        counter_driver <= 20'b0;        
    end 
    else if(cur_stat == nxt_stat )begin
        counter_driver <= counter_driver + 20'b1;        
    end
    else begin
        counter_driver <= 20'b0;        
    end
end 
//--------------------------------------------------------
reg[(CLK_DIVBIT + 2):0] stop_cntr;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        stop_cntr <= 'b0;        
    end 
    else if(cur_stat == STOP) begin 
        stop_cntr <= stop_cntr + 'b1;        
    end 
    else begin
        stop_cntr <= 'b0;        
    end
end 
//--------------------------------------------------------
reg iic_sclk_o;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        iic_sclk_o <= 1'b1;     
    end 
    else begin
        iic_sclk_o <= ((counter_driver[19:CLK_DIVBIT] >= MAX_TRANS)||
                       (cur_stat == IDLE)||
                       (cur_stat == START)||
                       (stop_cntr[CLK_DIVBIT +: 2] > 0))?1'b1:counter_driver[(CLK_DIVBIT - 1)];     
    end
end 
//--------------------------------------------------------
wire shift_sclk = counter_driver[(CLK_DIVBIT - 1)];  
reg  shift_sclk_ff1;
always @(posedge clk) shift_sclk_ff1 <= shift_sclk;
//--------------------------------------------------------
wire negedge_shift_sclk = (shift_sclk_ff1&(~shift_sclk))?1'b1:1'b0;
wire posedge_shift_sclk = (shift_sclk&(~shift_sclk_ff1))?1'b1:1'b0;
//--------------------------------------------------------
reg[7:0] data_load;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        data_load <= 8'b0;    
    end 
    else if(cur_stat == nxt_stat) begin
        if(negedge_shift_sclk) data_load <= {data_load[6:0],1'b0};
    end
    else if(nxt_stat == START) begin 
        data_load <= 8'h80;    
    end
    else if(nxt_stat == STOP) begin
        data_load <= 8'h40;    
    end
    else if(nxt_stat == PH_ID) begin 
        data_load <= (phase_cntr > 0)?{dev_id,1'b1}:{dev_id,1'b0}; 
    end 
    else if(nxt_stat == PH_A0) begin
        data_load <= sub_address[15:8];    
    end
    else if(nxt_stat == PH_A1) begin
        data_load <= sub_address[7:0];     
    end
    else if(nxt_stat == PH_D0) begin
        data_load <= write_data[7:0];     
    end
end 
//--------------------------------------------------------
reg  iic_sda_o;
reg  iic_sda_t;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        iic_data_read <= 16'b0;        
    end 
    else if((nxt_stat == PH_D0)&&(counter_driver[19:CLK_DIVBIT] < 8)&&posedge_shift_sclk) begin
        iic_data_read <= {iic_data_read[14:0],iic_sda};     
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        iic_sda_o <= 1'b1;    
    end 
    else if(cur_stat == IDLE) begin 
        iic_sda_o <= 1'b1;    
    end 
    else begin
        iic_sda_o <= data_load[7];
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        iic_sda_t <= 1'b0;    
    end 
    else if(cur_stat == IDLE) begin 
        iic_sda_t <= 1'b0;    
    end 
    else if((cur_stat == PH_D0)&&(s_op == 1)) begin
        iic_sda_t <= (counter_driver[19:CLK_DIVBIT] >= 8)?1'b0:1'b1;
    end
    else if(counter_driver[19:CLK_DIVBIT] >= 8) begin
        iic_sda_t <= 1'b1;    
    end
    else begin
        iic_sda_t <= 1'b0;    
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        ack_detec <= 1'b0;   
    end 
    else if((ack_detec==0)&&iic_sda_t)begin 
        ack_detec <= (iic_sda == 0)?1'b1:1'b0;  
    end 
    else if(~iic_sda_t)begin
        ack_detec <= 1'b0;   
    end
end 
//--------------------------------------------------------
assign iic_sda   = iic_sda_t?1'hz:iic_sda_o;
assign iic_sclk  = iic_sclk_o;

endmodule
